import functools
import json
import time
import pika
from pika.exchange_type import ExchangeType
import const
from utils import debug_util
from service import face_service

'''
rabbitmqConfig = {
        'username': 'nadou',
        'password': 'Asd17677142578',
        'port': 5672,
        'ip': '47.112.219.141',
}
'''
'''
rabbitmqConfig = {
        'username': 'nadou',
        'password': 'Asd17677142578',
        'port': 5672,
        'ip': '192.168.61.14',
}

'''
def get_connection_parameters():
    selection = 'rabbitmq'
    user = const.CFG.get(selection, 'user')
    pwd = const.CFG.get(selection, 'pwd')
    host = const.CFG.get(selection, 'host')
    port = const.CFG.get(selection, 'port')
    vhost = const.CFG.get(selection, 'vhost')
    credentials = pika.PlainCredentials(user, pwd)




    return pika.ConnectionParameters(host, int(port), vhost, credentials)

def get_connection():
    selection = 'rabbitmq'
    user = const.CFG.get(selection, 'user')
    pwd = const.CFG.get(selection, 'pwd')
    host = const.CFG.get(selection, 'host')
    port = const.CFG.get(selection, 'port')
    vhost = const.CFG.get(selection, 'vhost')
    credentials = pika.PlainCredentials(user, pwd)
    return pika.BlockingConnection(pika.ConnectionParameters(host, int(port), vhost, credentials))

def close(channel, connection): # 关闭RabbitMQ
    channel.close()
    connection.close()

def send_message(param, msg): # 发送消费情况

    hdrs = {'header': 'header'}
    properties = pika.BasicProperties(
        app_id=const.CFG.get('domain', 'host'),
        content_type='application/json',
        headers=hdrs)
    connect = get_connection()
    channel = connect.channel()
    channel.queue_declare(queue=param['queue'], durable=True)
    channel.basic_publish(exchange=param['exchange'], routing_key=param['routing_key'], body=json.dumps(msg, ensure_ascii=False), properties=properties)
    close(channel, connect)


def analysis_body(body):
    data = json.loads(body)
    return data['action'], data['stu_no']

def exec(action, stu_no):
    if action == const.ACTION.ALIGN_BUILD:
        debug_util.logger.error('align_and_build {}'.format(stu_no))
        face_service.align_and_build(stu_no)

class ExampleConsumer(object):
    EXCHANGE = const.MQ.EXCAHNGE
    EXCHANGE_TYPE = ExchangeType.topic
    QUEUE = const.MQ.QUEUE_FACE_PY
    ROUTING_KEY = const.MQ.QUEUE_FACE_PY
    def __init__(self):
        self.should_reconnect = False
        self.was_consuming = False
        self._connection = None
        self._channel = None
        self._closing = False
        self._consumer_tag = None
        self._consuming = False
        self._prefetch_count = 1

    def connect(self):
        print('Connecting')
        return pika.SelectConnection(
            parameters=get_connection_parameters(),
            on_open_callback=self.on_connection_open,
            on_open_error_callback=self.on_connection_open_error,
            on_close_callback=self.on_connection_closed)

    def close_connection(self):
        self._consuming = False
        if self._connection.is_closing or self._connection.is_closed:
            print('Connection is closing or already closed')
        else:
            print('Closing connection')
            self._connection.close()

    def on_connection_open(self, _unused_connection):
        print('Connection opened')
        self.open_channel()

    def on_connection_open_error(self, _unused_connection, err):

        print('Connection open failed: %s'.format(err))
        self.reconnect()

    def on_connection_closed(self, _unused_connection, reason):
        self._channel = None
        if self._closing:
            self._connection.ioloop.stop()
        else:
            print('Connection closed, reconnect necessary: %s'.format(reason))
            self.reconnect()

    def reconnect(self):
        self.should_reconnect = True
        self.stop()

    def open_channel(self):
        print('Creating a new channel')
        self._connection.channel(on_open_callback=self.on_channel_open)

    def on_channel_open(self, channel):
        print('Channel opened')
        self._channel = channel
        self.add_on_channel_close_callback()
        self.setup_exchange(self.EXCHANGE)

    def add_on_channel_close_callback(self):
        print('Adding channel close callback')
        self._channel.add_on_close_callback(self.on_channel_closed)

    def on_channel_closed(self, channel, reason):
        print('Channel %i was closed: %s', channel, reason)
        self.close_connection()

    def setup_exchange(self, exchange_name):
        print('Declaring exchange: %s', exchange_name)
        cb = functools.partial(
            self.on_exchange_declareok, userdata=exchange_name)
        self._channel.exchange_declare(
            exchange=exchange_name,
            exchange_type=self.EXCHANGE_TYPE,
            durable=True,
            callback=cb)

    def on_exchange_declareok(self, _unused_frame, userdata):
        print('Exchange declared: %s', userdata)
        self.setup_queue(self.QUEUE)

    def setup_queue(self, queue_name):
        print('Declaring queue %s', queue_name)
        cb = functools.partial(self.on_queue_declareok, userdata=queue_name)
        self._channel.queue_declare(queue=queue_name, callback=cb,
                                    durable=True)

    def on_queue_declareok(self, _unused_frame, userdata):
        queue_name = userdata
        print('Binding %s to %s with %s', self.EXCHANGE, queue_name,
                    self.ROUTING_KEY)
        cb = functools.partial(self.on_bindok, userdata=queue_name)
        self._channel.queue_bind(
            queue_name,
            self.EXCHANGE,
            routing_key=self.ROUTING_KEY,
            callback=cb)

    def on_bindok(self, _unused_frame, userdata):
        print('Queue bound: %s', userdata)
        self.set_qos()

    def set_qos(self):
        self._channel.basic_qos(
            prefetch_count=self._prefetch_count, callback=self.on_basic_qos_ok)

    def on_basic_qos_ok(self, _unused_frame):
        print('QOS set to: %d', self._prefetch_count)
        self.start_consuming()

    def start_consuming(self):
        print('Issuing consumer related RPC commands')
        self.add_on_cancel_callback()
        self._consumer_tag = self._channel.basic_consume(
            self.QUEUE, self.on_message, auto_ack=False)
        self.was_consuming = True
        self._consuming = True

    def add_on_cancel_callback(self):
        print('Adding consumer cancellation callback')
        self._channel.add_on_cancel_callback(self.on_consumer_cancelled)

    def on_consumer_cancelled(self, method_frame):
        print('Consumer was cancelled remotely, shutting down: %r',
                    method_frame)
        if self._channel:
            self._channel.close()

    def on_message(self, _unused_channel, basic_deliver, properties, body):
        action, stuno = analysis_body(body)
        debug_util.info('Received message delivery_tag:{0} appid: {1} stuno: {2} action: {3}'.format(basic_deliver.delivery_tag, properties.app_id, stuno, action))
        exec(action, stuno)
        self.acknowledge_message(basic_deliver.delivery_tag)

    def acknowledge_message(self, delivery_tag):
        debug_util.info('Acknowledging message {0}'.format(delivery_tag))
        self._channel.basic_ack(delivery_tag)

    def stop_consuming(self):
        if self._channel:
            print('Sending a Basic.Cancel RPC command to RabbitMQ')
            cb = functools.partial(
                self.on_cancelok, userdata=self._consumer_tag)
            self._channel.basic_cancel(self._consumer_tag, cb)

    def on_cancelok(self, _unused_frame, userdata):
        self._consuming = False
        print('RabbitMQ acknowledged the cancellation of the consumer: %s',userdata)
        self.close_channel()

    def close_channel(self):
        print('Closing the channel')
        self._channel.close()

    def run(self):
        self._connection = self.connect()
        self._connection.ioloop.start()

    def stop(self):
        if not self._closing:
            self._closing = True
            print('Stopping')
            if self._consuming:
                self.stop_consuming()
                self._connection.ioloop.start()
            else:
                self._connection.ioloop.stop()
            print('Stopped')

class ReconnectingExampleConsumer(object):
    def __init__(self):
        self._reconnect_delay = 0
        self._consumer = ExampleConsumer()

    def run(self):
        while True:
            try:
                self._consumer.run()
            except KeyboardInterrupt:
                self._consumer.stop()
                break
            self._maybe_reconnect()

    def _maybe_reconnect(self):
        if self._consumer.should_reconnect:
            self._consumer.stop()
            reconnect_delay = self._get_reconnect_delay()
            print('Reconnecting after %d seconds', reconnect_delay)
            time.sleep(reconnect_delay)
            self._consumer = ExampleConsumer()

    def _get_reconnect_delay(self):
        if self._consumer.was_consuming:
            self._reconnect_delay = 0
        else:
            self._reconnect_delay += 1
        if self._reconnect_delay > 30:
            self._reconnect_delay = 30
        return self._reconnect_delay

def start_consuming():
    consumer = ReconnectingExampleConsumer()
    consumer.run()

if __name__ == '__main__':
    start_consuming()